/**
  * @file       serialize.h
  * @author     const_zpc (any question please send mail to const_zpc@163.com)
  * @brief      数据序列化/反序列化函数实现 + 结构体序列化/反序列化实现
  * @version    0.1
  * @date       2023-04-01
  * 
  * 
  * ********************************************************************************************************************
  */
#ifndef COT_SERIALIZE_SERIALIZE_H
#define COT_SERIALIZE_SERIALIZE_H

#include "preprocessor/is_empty.h"
#include "preprocessor/for_each.h"
#include <stdint.h>

#ifdef __cplusplus
 extern "C" {
#endif 

/**
  * @brief 定义一个可序列化/反序列化的结构体
  * 
  * @attention  仅支持一维数组(字符串变量元素数目代表字符串预留长度)
  * @param name 结构体名字
  * @param val  结构体成员类型/标识符/元素数目
  * @code
  *         // 可在头文件中定义一个"test_t"结构体，
  *         // 成员分别是 {uint8_t val_1; int32_t val_2; double val_3[2]; char szDesc[32]}
  *         COT_DEFINE_STRUCT_TYPE(test_t
  *             ((UINT8_T)  (val_1)    (1))
  *             ((INT32_T)  (val_2)    (1))
  *             ((DOUBLE_T)  (val_3)   (2))
  *             ((STRING_T)  (szDesc)  (32))
  *         )
  * 
  *         // 在函数中定义变量
  *         int main()
  *         {
  *             uint8_t buf[100];
  * 
  *             COT_DEFINE_STRUCT_VARIABLE(test_t, testVal);
  *             // 或
  *             test_t testVal;
  *             COT_INIT_STRUCT_VARIABLE(test_t, testVal);
  * 
  *             testVal.val_1 = 5;
  *             testVal.val_2 = -15;
  *             testVal.val_3 = 2.25;
  *             sprintf(testVal.szDesc, "name");
  *             testVal.Serialize(buf,  &test); // 序列化
  *             testVal.Prase(&test, buf);  // 反序列化
  *         }
  * @endcode
  */
#define COT_DEFINE_STRUCT_TYPE(name, val)   \
    typedef struct stu##name\
    {\
        int (*Serialize)(uint8_t *out, const struct stu##name *p);\
        int (*Parse)(struct stu##name *p, const uint8_t *in);\
        COT_PP_SEQ_FOR_EACH(DEFINE_VALUE, , val)\
    }name;\
    static inline int name##Serialize(uint8_t *out, const struct stu##name *p)\
    {\
        uint8_t *ptr = out;\
        COT_PP_SEQ_FOR_EACH(DEFINE_PACK, (ptr) (p), val)\
        return ptr - out;\
    }\
    static inline int name##Prase(struct stu##name *p, const uint8_t *in)\
    {\
        uint8_t *ptr = (uint8_t *)in;\
        COT_PP_SEQ_FOR_EACH(DEFINE_UNPACK, (ptr) (p), val)\
        return ptr - in;\
    }


/**
  * @brief 定义并初始化一个可序列化/反序列化的结构体变量
  * 
  * @param name 结构体名字
  * @param val_name  结构体变量
  */
#define COT_DEFINE_STRUCT_VARIABLE(name, val_name)  name val_name = {name##Serialize, name##Prase}

/**
  * @brief 初始化一个可序列化/反序列化的结构体变量
  * 
  * @attention      需要先定义一个结构体变量
  * @param name     结构体名字
  * @param val_name 结构体变量
  */
#define COT_INIT_STRUCT_VARIABLE(name, val_name)  val_name.Serialize = name##Serialize; val_name.Parse = name##Prase;

/** 可序列化/反序列化的结构体成员使用的类型定义 */
typedef char      STRING_T;
typedef uint8_t   UINT8_T;
typedef uint16_t  UINT16_T;
typedef uint32_t  UINT32_T;
typedef uint64_t  UINT64_T;
typedef int8_t    INT8_T;
typedef int16_t   INT16_T;
typedef int32_t   INT32_T;
typedef int64_t   INT64_T;
typedef float     FLOAT_T;
typedef double    DOUBLE_T;



#define COT_TYPE(elem)                  COT_PP_SEQ_HEAD(elem)
#define COT_VARIABLE(elem)              COT_PP_SEQ_HEAD(COT_PP_SEQ_TAIL(elem))
#define COT_VARIABLE_SIZE(elem)         COT_PP_SEQ_HEAD(COT_PP_SEQ_TAIL(COT_PP_SEQ_TAIL(elem)))
#define COT_VARIABLE_ARR(elem, idx)     COT_PP_IIF(COT_PP_EQUAL(COT_VARIABLE_SIZE(elem), 1), ,  [idx])

#define DEFINE_VALUE(r, data, elem)                       \
  COT_TYPE(elem)                                 \
  COT_PP_IF(COT_PP_EQUAL(COT_PP_SEQ_SIZE(elem), 3), \
                COT_VARIABLE(elem) , )   \
  COT_VARIABLE_ARR(elem, COT_VARIABLE_SIZE(elem))\
  ;

#define VAL_UINT8_T                     UINT64_T
#define VAL_UINT16_T                    UINT64_T
#define VAL_UINT32_T                    UINT64_T
#define VAL_UINT64_T                    UINT64_T
#define VAL_INT8_T                      INT64_T
#define VAL_INT16_T                     INT64_T
#define VAL_INT32_T                     INT64_T
#define VAL_INT64_T                     INT64_T
#define VAL_FLOAT_T                     FLOAT_T
#define VAL_DOUBLE_T                    DOUBLE_T
#define VAL_STRING_T                    STRING_T

#define IS_STRING_UINT8_T               0
#define IS_STRING_UINT16_T              0
#define IS_STRING_UINT32_T              0
#define IS_STRING_UINT64_T              0
#define IS_STRING_INT8_T                0
#define IS_STRING_INT16_T               0
#define IS_STRING_INT32_T               0
#define IS_STRING_INT64_T               0
#define IS_STRING_FLOAT_T               0
#define IS_STRING_DOUBLE_T              0
#define IS_STRING_STRING_T              1

#define ENC_FUNC_UINT8_T                Cot_VarintEncoded
#define ENC_FUNC_UINT16_T               Cot_VarintEncoded
#define ENC_FUNC_UINT32_T               Cot_VarintEncoded
#define ENC_FUNC_UINT64_T               Cot_VarintEncoded
#define ENC_FUNC_INT8_T                 Cot_ZigzagEncoded
#define ENC_FUNC_INT16_T                Cot_ZigzagEncoded
#define ENC_FUNC_INT32_T                Cot_ZigzagEncoded
#define ENC_FUNC_INT64_T                Cot_ZigzagEncoded
#define ENC_FUNC_FLOAT_T                Cot_FloatEncoded
#define ENC_FUNC_DOUBLE_T               Cot_DoubleEncoded
#define ENC_FUNC_STRING_T               Cot_StringEncoded

#define DEC_FUNC_UINT8_T                Cot_VarintDecode
#define DEC_FUNC_UINT16_T               Cot_VarintDecode
#define DEC_FUNC_UINT32_T               Cot_VarintDecode
#define DEC_FUNC_UINT64_T               Cot_VarintDecode
#define DEC_FUNC_INT8_T                 Cot_ZigzagDecode
#define DEC_FUNC_INT16_T                Cot_ZigzagDecode
#define DEC_FUNC_INT32_T                Cot_ZigzagDecode
#define DEC_FUNC_INT64_T                Cot_ZigzagDecode
#define DEC_FUNC_FLOAT_T                Cot_FloatDecode
#define DEC_FUNC_DOUBLE_T               Cot_DoubleDecode
#define DEC_FUNC_STRING_T               Cot_StringDecode

#define DEFINE_DATA_PACK(r, data, elem)                       \
    for (int i = 0; i < COT_VARIABLE_SIZE(elem); i++){\
    COT_PP_SEQ_HEAD(data) = \
    COT_PP_CAT(ENC_FUNC_, COT_TYPE(elem))     \
    (COT_PP_SEQ_HEAD(data), \
    COT_PP_SEQ_HEAD(COT_PP_SEQ_TAIL(data))->COT_VARIABLE(elem)COT_VARIABLE_ARR(elem, i))\
    ;}

#define DEFINE_STRING_PACK(r, data, elem)                       \
    for (int i = 0; i < 1; i++){\
    COT_PP_SEQ_HEAD(data) = \
    COT_PP_CAT(ENC_FUNC_, COT_TYPE(elem))     \
    (COT_PP_SEQ_HEAD(data), &\
    COT_PP_SEQ_HEAD(COT_PP_SEQ_TAIL(data))->COT_VARIABLE(elem)COT_VARIABLE_ARR(elem, i))\
    ;}

#define DEFINE_PACK(r, data, elem)                       \
    COT_PP_IIF(COT_PP_CAT(IS_STRING_, COT_TYPE(elem)), DEFINE_STRING_PACK, DEFINE_DATA_PACK)(r, data, elem)


#define DEFINE_DATA_UNPACK(r, data, elem)                       \
    for (int i = 0; i < COT_VARIABLE_SIZE(elem); i++)       \
    {COT_PP_CAT(VAL_, COT_TYPE(elem)) tmp;\
    COT_PP_SEQ_HEAD(data) = \
    COT_PP_CAT(DEC_FUNC_, COT_TYPE(elem))     \
    (COT_PP_SEQ_HEAD(data), &tmp);\
    COT_PP_SEQ_HEAD(COT_PP_SEQ_TAIL(data))->COT_VARIABLE(elem)COT_VARIABLE_ARR(elem, i)=(COT_TYPE(elem))tmp;\
    }
    
#define DEFINE_STRING_UNPACK(r, data, elem)                       \
    for (int i = 0; i < 1; i++)       \
    {\
    COT_PP_SEQ_HEAD(data) = \
    COT_PP_CAT(DEC_FUNC_, COT_TYPE(elem))     \
    (COT_PP_SEQ_HEAD(data), &COT_PP_SEQ_HEAD(COT_PP_SEQ_TAIL(data))->COT_VARIABLE(elem)COT_VARIABLE_ARR(elem, i));\
    }

#define DEFINE_UNPACK(r, data, elem)  COT_PP_IIF(COT_PP_CAT(IS_STRING_, COT_TYPE(elem)), DEFINE_STRING_UNPACK, DEFINE_DATA_UNPACK)(r, data, elem)




extern uint8_t *Cot_VarintEncoded(uint8_t *ptr, uint64_t val);
extern uint8_t *Cot_VarintDecode(uint8_t *ptr, uint64_t *pVal);

extern uint8_t *Cot_ZigzagEncoded(uint8_t *ptr, int64_t val);
extern uint8_t *Cot_ZigzagDecode(uint8_t *ptr, int64_t *pVal);

extern uint8_t *Cot_FloatEncoded(uint8_t *ptr, float val);
extern uint8_t *Cot_FloatDecode(uint8_t *ptr, float *pVal);

extern uint8_t *Cot_DoubleEncoded(uint8_t *ptr, double val);
extern uint8_t *Cot_DoubleDecode(uint8_t *ptr, double *pVal);

extern uint8_t *Cot_StringEncoded(uint8_t *ptr, const char *pszString);
extern uint8_t *Cot_StringDecode(uint8_t *ptr, char *pszString);

#ifdef __cplusplus
 }
#endif

#endif